# Main.gd  (Godot 4.4)
extends Control

# ------------- 场景树要求（节点路径需一致）-------------
# Main(Control)
# ├─ PlayerPanel(Panel)/VBox(VBoxContainer)/{PlayerTitle, PlayerStats, HPBar(ProgressBar), MPBar(ProgressBar), PlayerStatus}
# ├─ EnemyPanel(Panel)/VBox(VBoxContainer)/{EnemyTitle, EnemyStats, EHPBar(ProgressBar), EnemyStatus}
# ├─ LogPanel(Panel)/Log(RichTextLabel)
# ├─ ActionBar(Panel)/Row(HBoxContainer)/{BtnAttack, BtnSkill, BtnItem, BtnGuard, BtnBarrier, BtnRun}
# ├─ ItemsPanel(Panel)/ItemsBox(VBoxContainer)  (visible=false)
# └─ SkillsPanel(Panel)/SkillsBox(VBoxContainer) (visible=false)

# ------------- Enums -------------
enum ItemType { POTION, ETHER, BOMB }
enum EquipSlot { WEAPON, ARMOR }
enum SkillType { POWER_SLASH, FIREBALL, HEAL, GUARD, BARRIER }
enum StatusType { POISON, BURN, STUN, SHIELD }

# ------------- 状态/装备构造 -------------
func _load_json(path:String) -> Dictionary:
	if not ResourceLoader.exists(path):
		return {}
	var s := FileAccess.get_file_as_string(path)
	var d = JSON.parse_string(s)
	if typeof(d) == TYPE_DICTIONARY:
		return d
	return {}

func add_item(name:String, t:int, amount:int, qty:int):
	var bag: Array = player["bag"] as Array
	for i in bag.size():
		var it:Dictionary = bag[i]
		if String(it["name"]) == name:
			it["qty"] = int(it["qty"]) + qty
			bag[i] = it
			player["bag"] = bag
			return
	bag.append({"name":name, "type":t, "amount":amount, "qty":qty})
	player["bag"] = bag

func _status_slots() -> Array[Dictionary]:
	return [
		{"active": false, "turns": 0, "power": 0}, # POISON
		{"active": false, "turns": 0, "power": 0}, # BURN
		{"active": false, "turns": 0, "power": 0}, # STUN
		{"active": false, "turns": 0, "power": 0}, # SHIELD
	]

func _make_equipment(name:String, slot:int, atk:int, def:int, crit:int=0, burn:int=0, lifesteal:int=0) -> Dictionary:
	return {
		"name": name, "slot": slot,
		"atk_bonus": atk, "def_bonus": def,
		"equipped": true,
		"crit": crit, "burn": burn, "lifesteal": lifesteal,
	}

# ------------- 全局状态 -------------
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
var player: Dictionary
var enemy: Dictionary
var wave: int = 1

# ------------- UI 引用（用 $ 路径，不需要唯一名） -------------
@onready var _fx: Control = $FxLayer
@onready var _p_title  : Label       = $PlayerPanel/VBox/PlayerTitle
@onready var _p_stats  : Label       = $PlayerPanel/VBox/PlayerStats
@onready var _p_hp     : ProgressBar = $PlayerPanel/VBox/HPBar
@onready var _p_mp     : ProgressBar = $PlayerPanel/VBox/MPBar
@onready var _p_status : Label       = $PlayerPanel/VBox/PlayerStatus

@onready var _e_title  : Label       = $EnemyPanel/VBox/EnemyTitle
@onready var _e_stats  : Label       = $EnemyPanel/VBox/EnemyStats
@onready var _e_hp     : ProgressBar = $EnemyPanel/VBox/EHPBar
@onready var _e_status : Label       = $EnemyPanel/VBox/EnemyStatus

@onready var _log      : RichTextLabel  = $LogPanel/Log
@onready var _items_box: VBoxContainer  = $ItemsPanel/ItemsBox
@onready var _skills_box: VBoxContainer = $SkillsPanel/SkillsBox
@onready var _items_panel: Panel        = $ItemsPanel
@onready var _skills_panel: Panel       = $SkillsPanel

@onready var _btn_attack : Button = $ActionBar/Row/BtnAttack
@onready var _btn_skill  : Button = $ActionBar/Row/BtnSkill
@onready var _btn_item   : Button = $ActionBar/Row/BtnItem
@onready var _btn_guard  : Button = $ActionBar/Row/BtnGuard
@onready var _btn_barrier: Button = $ActionBar/Row/BtnBarrier
@onready var _btn_run    : Button = $ActionBar/Row/BtnRun
@onready var _shop: Window = $Shop
# --- 装备窗口引用 ---
@onready var _equip_win   : Window        = $EquipWin
@onready var _equip_vbox  : VBoxContainer = $EquipWin/VBox
@onready var _cur_weapon  : Label         = $EquipWin/VBox/Current/CurWeapon
@onready var _cur_armor   : Label         = $EquipWin/VBox/Current/CurArmor
@onready var _weapons_box : VBoxContainer = $EquipWin/VBox/Tabs/WeaponsTab/WeaponsBox
@onready var _armors_box  : VBoxContainer = $EquipWin/VBox/Tabs/ArmorsTab/ArmorsBox
@onready var _btn_equip   : Button        = $ActionBar/Row/BtnEquip
@onready var _btn_equip_close : Button    = $EquipWin/VBox/BtnClose
@onready var _equip_tip      : Panel          = $EquipWin/EquipTip
@onready var _equip_tip_body : RichTextLabel  = $EquipWin/EquipTip/EquipTipBody

# --- 稀有度颜色 & 词条图标（与 Shop.gd 保持一致） ---
const RAR_COLORS := {0: Color(0.85,0.85,0.85), 1: Color(0.45,0.75,1.0), 2: Color(0.75,0.45,1.0)}
const ICON_CRIT := "★"     # 暴击
const ICON_BURN := "🔥"     # 燃烧
const ICON_LS   := "🩸"     # 吸血
# ------------- 工具函数 -------------
var _hp_text: Label
var _mp_text: Label
var _ehp_text: Label

# ===== 稀有度高亮 & 粒子 =====
func _flash_border_on(ctrl: Control, col: Color, dur: float=0.5, thickness: int=3, grow_px: int=8):
	# 取目标控件的全局矩形
	var r := ctrl.get_global_rect()
	# 在 FX 层添加一个 Panel，当做“边框”
	var p := Panel.new()
	p.mouse_filter = Control.MOUSE_FILTER_IGNORE
	_fx.add_child(p)
	# 初始尺寸略大一点（grow_px）
	p.position = r.position - Vector2(grow_px, grow_px)
	p.size = r.size + Vector2(grow_px*2, grow_px*2)
	# 边框样式：透明背景 + 有色边框
	p.add_theme_color_override("panel", Color(0,0,0,0))
	p.add_theme_constant_override("border_width_left", thickness)
	p.add_theme_constant_override("border_width_top", thickness)
	p.add_theme_constant_override("border_width_right", thickness)
	p.add_theme_constant_override("border_width_bottom", thickness)
	p.add_theme_color_override("border_color", col)
	p.modulate.a = 0.0

	var tw := create_tween()
	tw.tween_property(p, "modulate:a", 1.0, 0.12)
	tw.tween_property(p, "modulate:a", 0.0, dur).set_delay(0.08)
	tw.finished.connect(func(): p.queue_free())

func _spawn_sparkles_around(ctrl: Control, col: Color, count: int=10):
	# 在控件周围撒一些小星星
	var r := ctrl.get_global_rect()
	var center := r.position + r.size * 0.5
	for i in count:
		var lbl := Label.new()
		var ch := "★"
		if i % 3 == 1:
			ch = "✦"
		elif i % 3 == 2:
			ch = "✶"
		lbl.text = ch
		lbl.modulate = col
		lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
		_fx.add_child(lbl)
		# 随机起点与目标
		var angle := randf() * PI * 2.0
		var radius := randf_range(8.0, 28.0)
		var start := center + Vector2(randf_range(-6.0,6.0), randf_range(-6.0,6.0))
		var end := center + Vector2(cos(angle), sin(angle)) * (radius + randf_range(6.0, 36.0))
		lbl.position = start
		lbl.modulate.a = 0.0
		lbl.scale = Vector2(0.9, 0.9)
		var t := create_tween()
		t.tween_property(lbl, "position", end, 0.45).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
		t.parallel().tween_property(lbl, "modulate:a", 1.0, 0.08)
		t.parallel().tween_property(lbl, "scale", Vector2(1.1,1.1), 0.25)
		t.tween_property(lbl, "modulate:a", 0.0, 0.25)
		t.finished.connect(func(): lbl.queue_free())

func _rar_hex(c: Color) -> String:
	var r := int(clamp(c.r * 255.0, 0.0, 255.0))
	var g := int(clamp(c.g * 255.0, 0.0, 255.0))
	var b := int(clamp(c.b * 255.0, 0.0, 255.0))
	return "#%02X%02X%02X" % [r, g, b]

func _delta_line(label:String, delta:int) -> String:
	if delta > 0:
		return "[color=#3CCB77]%s %+d[/color]\n" % [label, delta]
	elif delta < 0:
		return "[color=#E05A5A]%s %+d[/color]\n" % [label, delta]
	else:
		return "[color=#9AA3AB]%s %+d[/color]\n" % [label, delta]

func _affix_lines(e:Dictionary) -> String:
	var s := ""
	var v:int = int(e.get("crit",0))
	if v > 0: s += "★ 暴击 +%d%%\n" % v
	v = int(e.get("burn",0))
	if v > 0: s += "🔥 燃烧 +%d%%\n" % v
	v = int(e.get("lifesteal",0))
	if v > 0: s += "🩸 吸血 +%d%%\n" % v
	return s

func _equip_compare_bbcode(slot:String, current:Dictionary, cand:Dictionary) -> String:
	var head_col = RAR_COLORS.get(int(cand.get("rarity",0)), Color(1,1,1))
	var head := "[b][color=%s]%s[/color][/b]\n" % [_rar_hex(head_col), String(cand.get("name",""))]
	# 计算差值
	var d_atk:int = int(cand.get("atk_bonus",0)) - int(current.get("atk_bonus",0))
	var d_def:int = int(cand.get("def_bonus",0)) - int(current.get("def_bonus",0))
	var body := _delta_line("ATK", d_atk) + _delta_line("DEF", d_def)
	var aff := _affix_lines(cand)
	if aff != "":
		body += "\n" + aff
	return head + body

func _show_equip_tip(panel: Panel, body: RichTextLabel, _global_pos: Vector2, text: String):
	body.clear()
	body.append_text(text)
	panel.visible = true

	# 以 Tooltip 的父控件（Window 内部的容器）作为坐标系
	var root: Control = panel.get_parent() as Control
	var pos := root.get_local_mouse_position() + Vector2(16, 16)
	var size := Vector2(
		max(320.0, body.get_content_width() + 24.0),
		min(480.0, body.get_content_height() + 16.0)
	)
	panel.size = size
	var root_size := root.size
	if pos.x + size.x > root_size.x:
		pos.x = root_size.x - size.x - 8.0
	if pos.y + size.y > root_size.y:
		pos.y = root_size.y - size.y - 8.0

	panel.position = pos


func _hide_equip_tip(panel: Panel):
	panel.visible = false

func _equip_show_tip_weapon(cand:Dictionary):
	var cur:Dictionary = player["equips"]["weapon"]
	var text := _equip_compare_bbcode("weapon", cur, cand)
	_show_equip_tip(_equip_tip, _equip_tip_body, get_viewport().get_mouse_position(), text)

func _equip_show_tip_armor(cand:Dictionary):
	var cur:Dictionary = player["equips"]["armor"]
	var text := _equip_compare_bbcode("armor", cur, cand)
	_show_equip_tip(_equip_tip, _equip_tip_body, get_viewport().get_mouse_position(), text)

func _equip_hide_tip():
	_hide_equip_tip(_equip_tip)



func _spawn_float_text(over_bar: ProgressBar, kind:int, text:String):
	var st:Dictionary = FT_STYLE[kind]
	var lbl := Label.new()
	lbl.text = text
	lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
	lbl.vertical_alignment   = VERTICAL_ALIGNMENT_CENTER
	lbl.modulate = st["color"]
	lbl.add_theme_color_override("font_outline_color", st["outline"])
	lbl.add_theme_constant_override("outline_size", int(st["outline_size"]))
	_fx.add_child(lbl)

	# 起点：血条中心上方一点，带随机水平抖动
	var r := over_bar.get_global_rect()
	var start := r.position + Vector2(r.size.x*0.5, -6)
	start.x += randf_range(-st["x_jitter"], st["x_jitter"])
	lbl.position = start

	# 旋转（可选）
	if st["rot"] != 0.0:
		var sgn := 1.0
		if randf() < 0.5:
			sgn = -1.0
		lbl.rotation_degrees = sgn * float(st["rot"])

	# 初始缩放 & 暴击弹性
	lbl.scale = Vector2(float(st["scale"]), float(st["scale"]))
	if bool(st["bounce"]):
		var tw_b := create_tween()
		tw_b.tween_property(lbl, "scale", Vector2(1.0, 1.0), 0.18).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)

	# 位移+淡出
	var tw := create_tween()
	tw.tween_property(lbl, "position:y", lbl.position.y - float(st["lift"]), float(st["dur"]))
	tw.parallel().tween_property(lbl, "modulate:a", 0.0, float(st["dur"]))
	tw.finished.connect(func(): lbl.queue_free())

func _rar_color(r:int) -> Color:
	if RAR_COLORS.has(r):
		return RAR_COLORS[r]
	return Color.WHITE

func _equip_affix_text(e:Dictionary) -> String:
	var aff := []
	if int(e.get("crit",0)) > 0:
		aff.append("%s+%d%%" % [ICON_CRIT, int(e["crit"])])
	if int(e.get("burn",0)) > 0:
		aff.append("%s+%d%%" % [ICON_BURN, int(e["burn"])])
	if int(e.get("lifesteal",0)) > 0:
		aff.append("%s+%d%%" % [ICON_LS, int(e["lifesteal"])])
	return "{%s}" % ", ".join(aff) if aff.size() > 0 else ""

func _attach_bar_text(bar: ProgressBar) -> Label:
	var t := Label.new()
	t.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
	t.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	t.size_flags_horizontal = Control.SIZE_EXPAND_FILL
	t.size_flags_vertical = Control.SIZE_EXPAND_FILL
	t.mouse_filter = Control.MOUSE_FILTER_IGNORE
	bar.add_child(t)
	t.anchor_left = 0.0; t.anchor_right = 1.0
	t.anchor_top = 0.0;  t.anchor_bottom = 1.0
	t.position = Vector2.ZERO
	t.size = bar.size
	return t

func _clear_children(node: Node) -> void:
	for c in node.get_children():
		c.queue_free()

func RR(a:int, b:int) -> int:
	return rng.randi_range(a, b)
# 暴击特效开始
# ========= 飘字样式 =========
enum FT { DMG, CRIT, DOT, HEAL, SHIELD_GAIN, SHIELD_BLOCK }

const FT_STYLE := {
	FT.DMG:          {"color": Color(0.86, 0.25, 0.25), "outline": Color(0,0,0), "outline_size": 2, "scale": 1.0, "lift": 42.0, "dur": 0.7, "x_jitter": 10.0, "rot": 0.0,  "bounce": false},
	FT.CRIT:         {"color": Color(1.00, 0.85, 0.20), "outline": Color(0.25,0.15,0.0), "outline_size": 3, "scale": 1.35,"lift": 56.0, "dur": 0.9, "x_jitter": 16.0, "rot": 8.0,  "bounce": true},
	FT.DOT:          {"color": Color(0.86, 0.25, 0.25), "outline": Color(0,0,0), "outline_size": 2, "scale": 0.9, "lift": 34.0, "dur": 0.7, "x_jitter": 6.0,  "rot": 0.0,  "bounce": false},
	FT.HEAL:         {"color": Color(0.25, 0.78, 0.47), "outline": Color(0,0,0), "outline_size": 2, "scale": 1.05,"lift": 44.0, "dur": 0.8, "x_jitter": 8.0,  "rot": 0.0,  "bounce": false},
	FT.SHIELD_GAIN:  {"color": Color(0.45, 0.70, 1.00), "outline": Color(0,0,0), "outline_size": 2, "scale": 1.0, "lift": 36.0, "dur": 0.7, "x_jitter": 6.0,  "rot": 0.0,  "bounce": false},
	FT.SHIELD_BLOCK: {"color": Color(0.55, 0.75, 1.00), "outline": Color(0,0,0), "outline_size": 2, "scale": 0.95,"lift": 28.0, "dur": 0.6, "x_jitter": 4.0,  "rot": 0.0,  "bounce": false},
}



# 便捷封装：在玩家/敌人头上显示
func FT_player(kind:int, val:int, prefix:String=""):
	var s := prefix
	if kind == FT.DMG or kind == FT.DOT or kind == FT.SHIELD_BLOCK:
		s += "-%d" % val
	else:
		s += "+%d" % val
	_spawn_float_text(_p_hp, kind, s)

func FT_enemy(kind:int, val:int, prefix:String=""):
	var s := prefix
	if kind == FT.DMG or kind == FT.DOT or kind == FT.SHIELD_BLOCK:
		s += "-%d" % val
	else:
		s += "+%d" % val
	_spawn_float_text(_e_hp, kind, s)

# ========= 打击感小工具 =========
func _panel_shake(panel:Control, pixels:int=8, dur:float=0.15):
	var orig := panel.position
	var steps := 6
	for i in steps:
		panel.position = orig + Vector2(randf_range(-pixels,pixels), randf_range(-pixels,pixels))
		await get_tree().create_timer(dur/float(steps)).timeout
	panel.position = orig

func _flash(panel:CanvasItem, dur:float=0.06):
	var orig := panel.modulate
	panel.modulate = Color(1,1,1)
	await get_tree().create_timer(dur).timeout
	panel.modulate = orig

func _hitstop(sec:float=0.06, scale:float=0.6):
	var old := Engine.time_scale
	Engine.time_scale = scale
	await get_tree().create_timer(sec, false, true).timeout
	Engine.time_scale = old

# 暴击特效结束
func clampi(v:int, lo:int, hi:int) -> int:
	return min(max(v, lo), hi)

func log_line(fmt:String, args:Array=[]):
	 # 默认白色
	_log.push_color(Color(1,1,1))
	_log.append_text(fmt % args + "\n")
	_log.pop()
	_log.scroll_to_line(_log.get_line_count()-1)
func log_col(col: Color, fmt:String, args:Array=[]):
	_log.push_color(col)
	_log.append_text(fmt % args + "\n")
	_log.pop()
	_log.scroll_to_line(_log.get_line_count()-1)
const C_DMG := Color(0.86, 0.25, 0.25)   # 红：伤害
const C_HEAL := Color(0.25, 0.78, 0.47)  # 绿：治疗
const C_SHIELD := Color(0.45, 0.70, 1.0) # 蓝：护盾
const C_WARN := Color(0.95, 0.75, 0.30)  # 黄：警示/蓄力
	

# ------------- 面板数据计算 -------------
func p_atk() -> int:
	var b:int = 0
	if player["equips"]["weapon"]["equipped"]:
		b += int(player["equips"]["weapon"]["atk_bonus"])
	if player["equips"]["armor"]["equipped"]:
		b += int(player["equips"]["armor"]["atk_bonus"])
	return int(player["atk"]) + b

func p_def() -> int:
	var b:int = 0
	if player["equips"]["weapon"]["equipped"]:
		b += int(player["equips"]["weapon"]["def_bonus"])
	if player["equips"]["armor"]["equipped"]:
		b += int(player["equips"]["armor"]["def_bonus"])
	return int(player["def"]) + b

func clamp_hpmp():
	player["hp"] = clampi(int(player["hp"]), 0, int(player["max_hp"]))
	player["mp"] = clampi(int(player["mp"]), 0, int(player["max_mp"]))

# ------------- 状态相关 -------------
func has_status(st:Array, t:int) -> bool:
	return st[t]["active"]

func apply_status(st:Array, t:int, turns:int, power:int, who:String):
	if t == StatusType.SHIELD:
		st[t]["active"] = true
		st[t]["power"] = int(st[t]["power"]) + power
		log_col(C_SHIELD, "%s获得护盾 +%d", [who, power])
		if who == "你":
			FT_player(FT.SHIELD_GAIN, power, "🛡")
		else:
			FT_enemy(FT.SHIELD_GAIN, power, "🛡")
	
		return
	st[t]["active"] = true
	st[t]["turns"] = max(int(st[t]["turns"]), turns)
	st[t]["power"] = power
	var names := ["中毒","燃烧","眩晕","护盾"]
	log_line("%s附加状态：%s(%d回合)", [who, names[t], int(st[t]["turns"])])

func dec_turn(st:Array, t:int):
	if not st[t]["active"]: return
	st[t]["turns"] = int(st[t]["turns"]) - 1
	if int(st[t]["turns"]) <= 0:
		st[t] = {"active": false, "turns": 0, "power": 0}

func absorb_shield(st:Array, dmg:int, who:String) -> int:
	if not st[StatusType.SHIELD]["active"] or int(st[StatusType.SHIELD]["power"]) <= 0:
		return dmg
	var ab:int = min(dmg, int(st[StatusType.SHIELD]["power"]))
	st[StatusType.SHIELD]["power"] = int(st[StatusType.SHIELD]["power"]) - ab
	var left:int = dmg - ab
	log_col(C_SHIELD, "%s的护盾吸收 %d 伤害(剩 %d)", [who, ab, max(0, int(st[StatusType.SHIELD]["power"]))])
	if int(st[StatusType.SHIELD]["power"]) <= 0:
		st[StatusType.SHIELD] = {"active": false, "turns": 0, "power": 0}
		# 飘字：显示被护盾抵挡的伤害
	if ab > 0:
		if who == "你":
			FT_player(FT.SHIELD_BLOCK, ab, "🛡")
		else:
			FT_enemy(FT.SHIELD_BLOCK, ab, "🛡")
	return left

# ------------- 战斗计算 -------------
func phys_damage(atk:int, def:int) -> int:
	var base:int = atk + RR(0,3) - int(def/2)
	return max(base, 1)

func deal_to_enemy(dmg:int) -> int:
	var left:int = absorb_shield(enemy["st"], dmg, String(enemy["name"]))
	if left <= 0: return 0
	enemy["hp"] = max(int(enemy["hp"]) - left, 0)
	return left

func deal_to_player(dmg:int) -> int:
	if player["guarding"]:
		dmg = int((dmg + 1) / 2)
		log_line("防御姿态：伤害减半")
	var left:int = absorb_shield(player["st"], dmg, "你")
	if left <= 0: return 0
	player["hp"] = max(int(player["hp"]) - left, 0)
	clamp_hpmp()
	return left

func weapon_on_hit(dealt:int, is_physical:bool):
	var w: Dictionary = player["equips"]["weapon"] as Dictionary
	if not w["equipped"]:
		return
	if int(w.get("lifesteal", 0)) > 0 and dealt > 0:
		var heal:int = int((dealt * int(w["lifesteal"]) + 99) / 100)
		player["hp"] = int(player["hp"]) + heal
		clamp_hpmp()
		log_col(C_HEAL, "[吸血] 回复 %d HP", [heal])
	if is_physical and int(w.get("burn", 0)) > 0 and RR(1,100) <= int(w["burn"]):
		apply_status(enemy["st"], StatusType.BURN, 3, 4, String(enemy["name"]))
		log_line("[燃烧] 触发")

func player_attack():
	var w: Dictionary = player["equips"]["weapon"] as Dictionary
	var wcrit:int = int(w.get("crit", 0))
	var crit: bool = (wcrit > 0 and RR(1,100) <= wcrit)
	var dmg:int = phys_damage(p_atk(), int(enemy["def"]))
	if crit:
		dmg = int(dmg * 1.6)
		log_line("暴击！")
	var dealt:int = deal_to_enemy(dmg)
	weapon_on_hit(dealt, true)
	log_col(C_DMG, "你对%s造成 %d 伤害", [String(enemy["name"]), dealt])
	if crit:
		FT_enemy(FT.CRIT, dealt)
	else:
		FT_enemy(FT.DMG, dealt)
	_panel_shake($EnemyPanel)
	_flash($EnemyPanel)
	_hitstop()


func enemy_attack():
	var dealt:int = deal_to_player(phys_damage(int(enemy["atk"]), p_def()))
	log_col(C_DMG, "%s 攻击你造成 %d 伤害", [String(enemy["name"]), dealt])
	FT_player(FT.DMG, dealt)
	_panel_shake($PlayerPanel)
	_flash($PlayerPanel)
	_hitstop()



func enemy_big():
	var dealt:int = deal_to_player(phys_damage(int(enemy["atk"])+6, p_def()) + 5)
	log_col(C_WARN, "%s 释放大招！%d 伤害", [String(enemy["name"]), dealt])

# ------------- 回合结算 -------------
func start_turn_player():
	if has_status(player["st"], StatusType.BURN):
		var d:int = max(1, int(player["st"][StatusType.BURN]["power"]))
		player["hp"] = int(player["hp"]) - d; clamp_hpmp()
		log_col(C_DMG, "燃烧：- %d HP", [d])
		dec_turn(player["st"], StatusType.BURN)
		FT_player(FT.DOT, d)

func end_turn_player():
	if has_status(player["st"], StatusType.POISON):
		var d:int = max(1, int(player["st"][StatusType.POISON]["power"]))
		player["hp"] = int(player["hp"]) - d; clamp_hpmp()
		log_col(C_DMG, "中毒：- %d HP", [d])
		dec_turn(player["st"], StatusType.POISON)
		FT_player(FT.DOT, d)

func start_turn_enemy():
	if has_status(enemy["st"], StatusType.BURN):
		var d:int = max(1, int(enemy["st"][StatusType.BURN]["power"]))
		enemy["hp"] = max(int(enemy["hp"]) - d, 0)
		log_col(C_DMG, "%s燃烧：-%d", [String(enemy["name"]), d])
		dec_turn(enemy["st"], StatusType.BURN)
		FT_enemy(FT.DOT, d)


func end_turn_enemy():
	if has_status(enemy["st"], StatusType.POISON):
		var d:int = max(1, int(enemy["st"][StatusType.POISON]["power"]))
		enemy["hp"] = max(int(enemy["hp"]) - d, 0)
		log_col(C_DMG, "%s中毒：-%d", [String(enemy["name"]), d])
		dec_turn(enemy["st"], StatusType.POISON)
		FT_enemy(FT.DOT, d)


# ------------- 敌人 AI -------------
func enemy_turn():
	start_turn_enemy()
	if int(enemy["hp"]) <= 0: return
	if has_status(enemy["st"], StatusType.STUN):
		log_line("%s眩晕，跳过行动", [String(enemy["name"])])
		dec_turn(enemy["st"], StatusType.STUN)
		end_turn_enemy()
		return
	if enemy.get("charging", false):
		enemy_big()
		enemy["charging"] = false
		end_turn_enemy()
		return
	var r:int = RR(1,100)
	if r <= 55:
		enemy_attack()
	elif r <= 70:
		log_col(C_WARN, "%s开始蓄力", [String(enemy["name"])])
		enemy["charging"] = true
	elif r <= 85:
		apply_status(player["st"], StatusType.POISON, RR(2,3), 4, "你")
	else:
		apply_status(enemy["st"], StatusType.SHIELD, 0, 12 + wave*2, String(enemy["name"]))
	end_turn_enemy()

# ------------- 技能/物品 -------------
func cast_skill(kind:int):
	var mp_cost: int = ({
		SkillType.POWER_SLASH:5, SkillType.FIREBALL:7,
		SkillType.HEAL:6,        SkillType.GUARD:3,
		SkillType.BARRIER:8
	}[kind]) as int
	if int(player["mp"]) < mp_cost:
		log_line("MP 不足"); return
	player["mp"] = int(player["mp"]) - mp_cost

	match kind:
		SkillType.POWER_SLASH:
			var w: Dictionary = player["equips"]["weapon"] as Dictionary
			var wcrit:int = int(w.get("crit", 0))
			var crit: bool = (wcrit > 0 and RR(1,100) <= wcrit)
			var dmg:int = phys_damage(p_atk()+5, int(enemy["def"]))
			if crit:
				dmg = int(dmg * 1.6); log_line("暴击！")
			var dealt:int = deal_to_enemy(dmg)
			weapon_on_hit(dealt, true)
			if RR(1,100) <= 20:
				apply_status(enemy["st"], StatusType.STUN, 1, 0, String(enemy["name"]))
			log_line("施放【重击】")

		SkillType.FIREBALL:
			var dmg2:int = 10 + RR(0,6)
			var dealt2:int = deal_to_enemy(dmg2)
			if RR(1,100) <= 40:
				apply_status(enemy["st"], StatusType.BURN, 3, 4, String(enemy["name"]))
			log_line("施放【火球】")

		SkillType.HEAL:
			var heal:int = 18 + RR(0,4)
			player["hp"] = int(player["hp"]) + heal; clamp_hpmp()
			log_col(C_HEAL, "施放【治疗】：+%d HP", [heal])
			FT_player(FT.HEAL, heal)

		SkillType.GUARD:
			player["guarding"] = true
			log_line("施放【防御】：本回合减半伤害")

		SkillType.BARRIER:
			apply_status(player["st"], StatusType.SHIELD, 0, 20, "你")
			log_line("施放【结界】：获得护盾")

func use_item(idx:int):
	var bag: Array = player["bag"] as Array
	if idx < 0 or idx >= bag.size(): return
	var it: Dictionary = bag[idx] as Dictionary
	if int(it["qty"]) <= 0:
		log_line("该物品已用完"); return

	match int(it["type"]):
		ItemType.POTION:
			player["hp"] = int(player["hp"]) + int(it["amount"]); clamp_hpmp()
			it["qty"] = int(it["qty"]) - 1
			log_line("使用药水：+%d HP", [int(it["amount"])])

		ItemType.ETHER:
			player["mp"] = int(player["mp"]) + int(it["amount"]); clamp_hpmp()
			it["qty"] = int(it["qty"]) - 1
			log_line("使用以太：+%d MP", [int(it["amount"])])

		ItemType.BOMB:
			var d:int = int(it["amount"]) + RR(0,5)
			var dealt:int = deal_to_enemy(d)
			it["qty"] = int(it["qty"]) - 1
			log_line("你丢出炸弹，造成 %d", [dealt])

	bag[idx] = it
	player["bag"] = bag

# ------------- 初始化 -------------
func setup_player():
	player = {
		"name": "蛋挞",
		"hp": 85, "mp": 34, "max_hp": 85, "max_mp": 34,
		"atk": 12, "def": 6,
		"equips": {
			"weapon": _make_equipment("铁剑[普通]", EquipSlot.WEAPON, 4, 0),
			"armor" : _make_equipment("皮甲[普通]", EquipSlot.ARMOR,  0, 3),
		},
		"bag": [],
		"st": _status_slots(),
		"guarding": false,
		"gold": 100,
	}
	player["bag"] = []
	var starters:Array = DB.items.get("starters", [])
	for e in starters:
		player["bag"].append({
			"name": String(e.get("name","")),
			"type": int(e.get("type",0)),
			"amount": int(e.get("amount",0)),
			"qty": int(e.get("qty",1))
		})
	_init_player_skills_from_db()
	player["equip_bag_weapons"] = []   # Array[Dictionary]
	player["equip_bag_armors"]  = []   # Array[Dictionary]

func setup_enemy():
	var names:Array = DB.enemies.get("names", ["史莱姆"])
	var base:Dictionary = DB.enemies.get("base", {"atk":9,"def":4,"hp":65})
	var per:Dictionary  = DB.enemies.get("per_wave", {"atk":2,"def":2,"hp":12,"hp_var_low":-5,"hp_var_high":8})
	var name = names[RR(0, names.size()-1)]
	var atk  := int(base.get("atk",9)) + wave*int(per.get("atk",2)) + RR(0,2)
	var def  := int(base.get("def",4)) + wave*int(per.get("def",2)) + RR(0,2)
	var hp   := int(base.get("hp",65)) + wave*int(per.get("hp",12)) + RR(int(per.get("hp_var_low",-5)), int(per.get("hp_var_high",8)))
	enemy = {"name":name, "hp":hp, "atk":atk, "def":def, "st":_status_slots(), "charging":false}
	enemy["max_hp"] = int(enemy["hp"])

func _init_player_skills_from_db():
	var order:Array = DB.skill_order
	player["skill_ids"] = order.duplicate()
	var costs := {}
	for sid in order:
		var d := DB.get_skill(String(sid))
		costs[String(sid)] = int(d.get("mp", 0))
	player["skill_costs"] = costs


# ------------- UI 刷新 -------------
func _status_text(st:Array) -> String:
	var pieces: Array[String] = []
	if not (has_status(st, StatusType.POISON) or has_status(st, StatusType.BURN)
		or has_status(st, StatusType.STUN) or has_status(st, StatusType.SHIELD)):
		return "状态：无"
	if has_status(st, StatusType.POISON): pieces.append("中毒(%d)" % int(st[StatusType.POISON]["turns"]))
	if has_status(st, StatusType.BURN):   pieces.append("燃烧(%d)" % int(st[StatusType.BURN]["turns"]))
	if has_status(st, StatusType.STUN):   pieces.append("眩晕(%d)" % int(st[StatusType.STUN]["turns"]))
	if has_status(st, StatusType.SHIELD): pieces.append("护盾(%d)" % int(st[StatusType.SHIELD]["power"]))
	return "状态：" + "，".join(pieces)

func update_ui():
	_p_title.text = "战士: %s" % String(player["name"])
	_p_stats.text = "攻击力 %d    防御力 %d   金币 %d" % [p_atk(), p_def(), int(player["gold"])]
	_p_hp.max_value = int(player["max_hp"]); _p_hp.value = int(player["hp"])
	_p_mp.max_value = int(player["max_mp"]); _p_mp.value = int(player["mp"])
	_p_status.text = _status_text(player["st"])

	_e_title.text = "波 %d  敌人 %s" % [wave, String(enemy["name"])]
	_e_stats.text = "攻击力 %d    防御力 %d" % [int(enemy["atk"]), int(enemy["def"])]
	_e_hp.max_value = int(enemy["max_hp"]); _e_hp.value = int(enemy["hp"])
	_e_status.text = _status_text(enemy["st"]) + ("（蓄力中）" if enemy.get("charging", false) else "")
	_hp_text.text = "%d / %d" % [int(player["hp"]), int(player["max_hp"])]
	_mp_text.text = "%d / %d" % [int(player["mp"]), int(player["max_mp"])]
	_ehp_text.text = "%d / %d" % [int(enemy["hp"]), int(enemy["max_hp"])]

# ------------- 战斗流程 -------------
func full_turn_end():
	end_turn_player()
	update_ui()
	if int(enemy["hp"]) > 0 and int(player["hp"]) > 0:
		enemy_turn()
	update_ui()
	_check_end()

func _check_end():
	if int(player["hp"]) <= 0:
		log_line("你被击倒了…… 按 F5 重新运行")
	elif int(enemy["hp"]) <= 0:
		var gold_gain:int = 45 + wave*20 + RR(0,10)
		player["gold"] = int(player["gold"]) + gold_gain
		log_line("打败 %s！获得金币 %d", [String(enemy["name"]), gold_gain])
		var pick:Dictionary = DB.get_drop_entry()
		if pick.size() > 0:
			add_item(String(pick.get("name","")), int(pick.get("type",0)), int(pick.get("amount",0)), 1)
			log_line("恭喜你获得：%s x1", [pick.get("name","")])
			var drop_col := Color(1.0, 0.85, 0.2)
			_flash_border_on($LogPanel, drop_col)
			_spawn_sparkles_around($LogPanel, drop_col)
		_shop.call("open_shop", wave, int(player["gold"]), player["equips"]["weapon"], player["equips"]["armor"])
		return
func _on_shop_buy_item(item_type:int, amount:int, price:int):
	if int(player["gold"]) < price:
		log_line("金币不足"); return
	match item_type:
		ItemType.POTION: add_item("药水", ItemType.POTION, 30, 1)
		ItemType.ETHER:  add_item("以太", ItemType.ETHER, 15, 1)
		ItemType.BOMB:   add_item("炸弹", ItemType.BOMB, 25, 1)
	player["gold"] = int(player["gold"]) - price
	_shop.call("update_gold", int(player["gold"]))
	log_line("购买成功")
	update_ui()

func _on_shop_buy_weapon(equip:Dictionary, price:int):
	if int(player["gold"]) < price:
		log_line("金币不足"); return
	# 旧武器入包，再装备新武器
	var ws:Array = player["equip_bag_weapons"] as Array
	ws.append(player["equips"]["weapon"])
	player["equip_bag_weapons"] = ws
	player["equips"]["weapon"] = equip
	player["gold"] = int(player["gold"]) - price
	_shop.call("update_gold", int(player["gold"]))
	log_line("购买并装备：%s", [String(equip["name"])])
	update_ui()
	# 稀有度颜色（你已有 RAR_COLORS）
	var col = RAR_COLORS.get(int(equip.get("rarity",0)), Color(1,1,1))
	_flash_border_on($PlayerPanel, col)
	_spawn_sparkles_around($PlayerPanel, col)

func _on_shop_buy_armor(equip:Dictionary, price:int):
	if int(player["gold"]) < price:
		log_line("金币不足"); return
	var ars:Array = player["equip_bag_armors"] as Array
	ars.append(player["equips"]["armor"])
	player["equip_bag_armors"] = ars
	player["equips"]["armor"] = equip
	player["gold"] = int(player["gold"]) - price
	_shop.call("update_gold", int(player["gold"]))
	log_line("购买并装备：%s", [String(equip["name"])])
	update_ui()
	# 掉落闪一下（用暖色）
	var col = RAR_COLORS.get(int(equip.get("rarity",0)), Color(1,1,1))
	_flash_border_on($PlayerPanel, col)
	_spawn_sparkles_around($PlayerPanel, col)


func _on_shop_closed():
	wave += 1
	setup_enemy()
	update_ui()
	log_line("新的敌人出现：%s", [String(enemy["name"])])
func _weighted_pick(entries:Array) -> Dictionary:
	var total:int = 0
	for e in entries:
		total += int(e.get("weight", 0))
	if total <= 0: return {}
	var r:int = RR(1, total)
	var acc:int = 0
	for e in entries:
		acc += int(e.get("weight", 0))
		if r <= acc:
			return e
	return entries.back()

func _open_equip_window():
	_rebuild_equip_window()
	_equip_win.popup_centered()
	var ws:Array = player["equip_bag_weapons"] as Array
	for i in range(ws.size()):
		var cand:Dictionary = ws[i]
		var b := Button.new()
		b.text = "%s  ATK%+d DEF%+d  %s" % [
			String(cand["name"]), int(cand["atk_bonus"]), int(cand["def_bonus"]), _equip_affix_text(cand)
		]
		b.add_theme_color_override("font_color", _rar_color(int(cand.get("rarity",0))))
		# 点击：装备这把
		b.pressed.connect(func(idx := i): _equip_weapon_from_bag(idx))

		# 悬停：显示对比（用 bind 传 cand）
		b.mouse_entered.connect(Callable(self, "_equip_show_tip_weapon").bind(cand))
		b.mouse_exited.connect(Callable(self, "_equip_hide_tip"))
		_weapons_box.add_child(b)
	var ars:Array = player["equip_bag_armors"] as Array
	for j in range(ars.size()):
		var cand_a:Dictionary = ars[j]
		var b2 := Button.new()
		b2.text = "%s  ATK%+d DEF%+d" % [
			String(cand_a["name"]), int(cand_a["atk_bonus"]), int(cand_a["def_bonus"])
		]
		b2.add_theme_color_override("font_color", _rar_color(int(cand_a.get("rarity",0))))

		b2.pressed.connect(func(idx := j): _equip_armor_from_bag(idx))
		b2.mouse_entered.connect(Callable(self, "_equip_show_tip_armor").bind(cand_a))
		b2.mouse_exited.connect(Callable(self, "_equip_hide_tip"))

		_armors_box.add_child(b2)
	

	


func _rebuild_equip_window():
	_cur_weapon.text = "当前武器：%s  ATK%+d DEF%+d  %s" % [
		String(player["equips"]["weapon"]["name"]),
		int(player["equips"]["weapon"]["atk_bonus"]),
		int(player["equips"]["weapon"]["def_bonus"]),
		_equip_affix_text(player["equips"]["weapon"])
	]
	_cur_armor.text  = "当前护甲：%s  ATK%+d DEF%+d" % [
		String(player["equips"]["armor"]["name"]),
		int(player["equips"]["armor"]["atk_bonus"]),
		int(player["equips"]["armor"]["def_bonus"])
	]

	_clear_children(_weapons_box)
	_clear_children(_armors_box)

	# 背包里的武器
	var ws:Array = player["equip_bag_weapons"] as Array
	for i in ws.size():
		var e:Dictionary = ws[i]
		var b := Button.new()
		b.text = "%s  ATK%+d DEF%+d  %s" % [
			String(e["name"]), int(e["atk_bonus"]), int(e["def_bonus"]), _equip_affix_text(e)
		]
		b.add_theme_color_override("font_color", _rar_color(int(e.get("rarity",0))))
		b.pressed.connect(func():
			_equip_weapon_from_bag(i)
		)
		_weapons_box.add_child(b)
		
	# 背包里的护甲
	var ars:Array = player["equip_bag_armors"] as Array
	for j in ars.size():
		var a:Dictionary = ars[j]
		var b2 := Button.new()
		b2.text = "%s  ATK%+d DEF%+d" % [
			String(a["name"]), int(a["atk_bonus"]), int(a["def_bonus"])
		]
		b2.add_theme_color_override("font_color", _rar_color(int(a.get("rarity",0))))
		b2.pressed.connect(func():
			_equip_armor_from_bag(j)
		)
		_armors_box.add_child(b2)
func _equip_weapon_from_bag(idx:int):
	var ws:Array = player["equip_bag_weapons"] as Array
	if idx < 0 or idx >= ws.size():
		return
	var new_w:Dictionary = ws[idx]
	var old_w:Dictionary = player["equips"]["weapon"]
	# 替换 & 旧的入包
	player["equips"]["weapon"] = new_w
	ws.remove_at(idx)
	ws.append(old_w)
	player["equip_bag_weapons"] = ws
	log_line("装备武器：%s", [String(new_w["name"])])
	update_ui()
	_rebuild_equip_window()

func _equip_armor_from_bag(idx:int):
	var ars:Array = player["equip_bag_armors"] as Array
	if idx < 0 or idx >= ars.size():
		return
	var new_a:Dictionary = ars[idx]
	var old_a:Dictionary = player["equips"]["armor"]
	player["equips"]["armor"] = new_a
	ars.remove_at(idx)
	ars.append(old_a)
	player["equip_bag_armors"] = ars
	log_line("装备护甲：%s", [String(new_a["name"])])
	update_ui()
	_rebuild_equip_window()
func _apply_effect(sid:String, eff:Dictionary):
	var typ := String(eff.get("type",""))
	var sk_name := String(DB.get_skill(sid).get("name",""))

	if typ == "damage":
		var atk:int = p_atk()
		var flat:int = int(eff.get("flat",0))
		var scale_atk:float = float(eff.get("scale_atk",0.0))
		var extra:int = int(round(atk * scale_atk))
		var raw:int = atk + flat + extra

		var dmg:int = phys_damage(raw, int(enemy["def"]))
		var w:Dictionary = player["equips"]["weapon"] as Dictionary
		var wcrit:int = int(w.get("crit",0))
		var is_crit:bool = false
		if wcrit > 0 and RR(1,100) <= wcrit:
			is_crit = true
			dmg = int(dmg * 1.6)
			log_line("暴击！")
		var dealt:int = deal_to_enemy(dmg)
		weapon_on_hit(dealt, true)
		if is_crit:
			FT_enemy(FT.CRIT, dealt)
		else:
			FT_enemy(FT.DMG, dealt)

		var sc:int = int(eff.get("stun_chance",0))
		if sc > 0 and RR(1,100) <= sc:
			var st_turns:int = int(eff.get("stun_turns",1))
			apply_status(enemy["st"], StatusType.STUN, st_turns, 0, String(enemy["name"]))
		log_line("施放【%s】", [sk_name])

	elif typ == "magic_damage":
		var base:int = int(eff.get("flat",0))
		var rl:int   = int(eff.get("rand_low",0))
		var rh:int   = int(eff.get("rand_high",0))
		var dmg2:int = base + RR(rl, rh)
		var dealt2:int = deal_to_enemy(dmg2)
		FT_enemy(FT.DMG, dealt2)
		log_line("施放【%s】", [sk_name])

	elif typ == "heal":
		var base_h:int = int(eff.get("flat",0))
		var rlh:int    = int(eff.get("rand_low",0))
		var rhh:int    = int(eff.get("rand_high",0))
		var heal:int   = base_h + RR(rlh, rhh)
		player["hp"] = int(player["hp"]) + heal
		clamp_hpmp()
		FT_player(FT.HEAL, heal)
		log_line("施放【%s】：+%d HP", [sk_name, heal])

	elif typ == "guard":
		player["guarding"] = true
		log_line("施放【%s】：本回合减半伤害", [sk_name])

	elif typ == "shield":
		var amt:int = int(eff.get("amount",20))
		apply_status(player["st"], StatusType.SHIELD, 0, amt, "你")
		log_line("施放【%s】：获得护盾", [sk_name])

	elif typ == "apply_status":
		var id_str := String(eff.get("status",""))
		var turns:int = int(eff.get("turns",1))
		var power:int = int(eff.get("power",0))
		var chance:int= int(eff.get("chance",100))
		var idx:int = int(DB.status_index.get(id_str, -1))
		if idx >= 0 and RR(1,100) <= chance:
			apply_status(enemy["st"], idx, turns, power, String(enemy["name"]))

func cast_skill_id(sid:String):
	var sk := DB.get_skill(sid)
	var mp_cost:int = int(sk.get("mp",0))
	if int(player["mp"]) < mp_cost:
		log_line("MP 不足"); return
	player["mp"] = int(player["mp"]) - mp_cost

	var effects:Array = sk.get("effects", [])
	for eff in effects:
		_apply_effect(sid, eff)  # 逐条执行


func _rebuild_skills_panel():
	_clear_children(_skills_box)
	var order:Array = player.get("skill_ids", [])
	for sid in order:
		var d := DB.get_skill(String(sid))
		var b := Button.new()
		b.text = "%s  MP:%d" % [String(d.get("name","")), int(d.get("mp",0))]
		b.pressed.connect(func(sid_keep := String(sid)):
			cast_skill_id(sid_keep)
			_skills_panel.visible = false
			full_turn_end()
		)
		_skills_box.add_child(b)

# ------------- 入口 -------------
func _ready():
	# 连接 DataDB 热加载信号
	DB.reloaded.connect(func():
		log_line("数据已热加载")
		_init_player_skills_from_db()
		update_ui()
		if _skills_panel.visible:
			_rebuild_skills_panel()
	# 商店窗口如果开着，会由 Shop.gd 自己刷新（见后）
	)
	_equip_win.size = Vector2i(900, 600)
	_equip_win.min_size = Vector2i(720, 480)
	_equip_win.close_requested.connect(func(): _equip_win.hide())
	rng.randomize()
	setup_player()
	setup_enemy()
	_hp_text = _attach_bar_text(_p_hp)
	_mp_text = _attach_bar_text(_p_mp)
	_ehp_text = _attach_bar_text(_e_hp)
	_p_hp.show_percentage = false
	_p_mp.show_percentage = false
	_e_hp.show_percentage = false

	# 统一设置 VBox 的子项间距（4.x 用主题常量）
	$PlayerPanel/VBox.add_theme_constant_override("separation", 18)
	$EnemyPanel/VBox.add_theme_constant_override("separation", 8)

	# 在 MP 条后插入一个空白控件，再放“状态”到其后 —— 玩家面板
	var pbox := $PlayerPanel/VBox
	var spacer_p := Control.new()
	spacer_p.custom_minimum_size = Vector2(0, 24)   # 你可以调大/调小
	pbox.add_child(spacer_p)
	var idx_mp:int = _p_mp.get_index()              # ← 用 child.get_index()
	pbox.move_child(spacer_p, idx_mp + 1)
	pbox.move_child(_p_status, spacer_p.get_index() + 1)

	# 敌人面板同理
	var ebox := $EnemyPanel/VBox
	var spacer_e := Control.new()
	spacer_e.custom_minimum_size = Vector2(0, 14)
	ebox.add_child(spacer_e)
	var idx_ehp:int = _e_hp.get_index()
	ebox.move_child(spacer_e, idx_ehp + 1)
	ebox.move_child(_e_status, spacer_e.get_index() + 1)

	


	update_ui()
	log_line("欢迎，勇者！点击按钮或使用快捷键。")
	_btn_attack.pressed.connect(func():
		player["guarding"] = false
		start_turn_player()
		if has_status(player["st"], StatusType.STUN):
			log_line("你被眩晕，回合跳过")
			dec_turn(player["st"], StatusType.STUN)
			full_turn_end()
		else:
			player_attack()
			full_turn_end()
	)
	_btn_skill.pressed.connect(func():
		_clear_children(_skills_box)
		var data: Array[Dictionary] = [
			{"name":"重击", "k":SkillType.POWER_SLASH},
			{"name":"火球", "k":SkillType.FIREBALL},
			{"name":"治疗", "k":SkillType.HEAL},
			{"name":"防御", "k":SkillType.GUARD},
			{"name":"结界", "k":SkillType.BARRIER},
		]
		for i in data.size():
			var e:Dictionary = data[i]
			var b := Button.new()
			b.text = String(e["name"])
			b.pressed.connect(func():
				cast_skill(int(e["k"]))
				_skills_panel.visible = false
				full_turn_end()
			)
			_skills_box.add_child(b)
		_skills_panel.visible = true
		_items_panel.visible = false
	)
	_btn_item.pressed.connect(func():
		_clear_children(_items_box)
		var bag: Array = player["bag"] as Array
		for i in bag.size():
			var it:Dictionary = bag[i] as Dictionary
			var b := Button.new()
			b.text = "%d) %s x%d" % [i+1, String(it["name"]), int(it["qty"])]
			b.pressed.connect(func():
				use_item(i)
				_items_panel.visible = false
				full_turn_end()
			)
			_items_box.add_child(b)
		_items_panel.visible = true
		_skills_panel.visible = false
	)
	_btn_guard.pressed.connect(func():
		player["guarding"] = false
		start_turn_player()
		player["guarding"] = true
		log_line("进入防御姿态")
		full_turn_end()
	)
	_btn_barrier.pressed.connect(func():
		player["guarding"] = false
		start_turn_player()
		cast_skill(SkillType.BARRIER)
		full_turn_end()
	)
	_btn_run.pressed.connect(func():
		if RR(1,100) <= 35:
			log_line("你成功逃跑了，刷新敌人！")
			setup_enemy()
			update_ui()
		else:
			log_line("逃跑失败")
			enemy_turn()
			update_ui()
	)
	_shop.connect("buy_item", Callable(self, "_on_shop_buy_item"))
	_shop.connect("buy_weapon", Callable(self, "_on_shop_buy_weapon"))
	_shop.connect("buy_armor", Callable(self, "_on_shop_buy_armor"))
	_shop.connect("shop_closed", Callable(self, "_on_shop_closed"))
	_btn_equip.pressed.connect(func(): _open_equip_window())
	_btn_equip_close.pressed.connect(func(): _equip_win.hide())
